/*
 *  Copyright (C) 2010 Glauco
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.balisunrise.annotation.util;

import java.lang.reflect.Field;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Properties;
import org.balisunrise.annotation.FieldDefinition;
import org.balisunrise.annotation.FieldEmbedded;
import org.balisunrise.annotation.FieldSelection;
import org.balisunrise.annotation.FilterType;
import org.balisunrise.annotation.PanelField;
import org.balisunrise.annotation.Selection;
import org.balisunrise.application.Application;
import org.balisunrise.common.PropertiesReader;

/**
 *
 * @author Glauco
 */
public class FieldUtilities {
    
    private static final String MESSAGE = "Erro ao ler Annotation \"PanelField\".";

    public static final int DEFINITION = 1;
    public static final int EMBEDDED = 2;
    public static final int SELECTION = 3;

    /**
     * Atributo com a anotação <code>@PanelField</code> da classe de uma entidade.
     */
    public Field field;

    /**
     * Alias (nome) da definição.
     */
    public String alias;
    
    /**
     * Indice em que o atributo deve aparecer na tela.
     */
    public int index;

    /**
     * String do label que aparecerá na frente do componente que edita um
     * atributo de uma entidade.
     */
    public String label;

    /**
     * String do label que aparecerá após o componente que edita um atributo de
     * uma entidade.
     */
    public String infoLabel;

    /**
     * Informa se os componentes do atributo serão postos em uma nova linha.
     */
    public boolean jumpToNextRow;

    /**
     * Informa se o bind do componente com o atributo será realizado apenas após
     * o evento <code>FocusLost</code> do componente.
     */
    public boolean bindOnFocusLost;

    /**
     * Infoma se o bind co componente com o atributo é somente de leitura do
     * atributo.
     */
    public boolean bindReadOnly;

    /**
     * Alias do atributo que será usada para construir os campo de um painel
     * de uma entidade associada encapsulada.
     */
    public String call;

    /**
     * alias da tela de pesquisa do atributo de seleção.
     */
    public String search;

    /**
     * Mensagem para quando uma entidade de seleção não foi encontrada.
     */
    public String notFoundMessage;

    /**
     * Tipo da anotação de <code>@PanelField</code>.
     * @see FieldUtilities#DEFINITION
     * @see FieldUtilities#EMBEDDED
     * @see FieldUtilities#SELECTION
     */
    public int type;

    /**
     * Alinhamento.
     */
    public int aligment;

    /**
     * Propriedades do campo da entidade.
     */
    public Properties properties;

    /**
     * Determina se este campo é utilizado para seleção de entidades.
     */
    public boolean selection;

    /**
     * Tipo de filtro para a seleção.
     */
    public FilterType selectionFilterType;

    /**
     * Campos encapsulados ou de seleção.
     */
    public List<FieldUtilities> embedded;

    /**
     * Cria FieldUtilities a partir de um campo (Field) e um alias.
     * É lido as anotações do campo e crido a definição.
     * @param field Campo com as anotações a serem lidas.
     * @param alias Alias para busca a anotação.
     * @return Retorna FieldUtilities se exitir a anotação com o alias.
     * @throws AnnotationsUtilitiesException se não encontrar a anotação ou o
     * alias.
     */
    public static FieldUtilities create(Field field, String alias)
            throws AnnotationsUtilitiesException{

        if (field == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"field\" não pode ser nulo!");

        if (alias == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"alias\" não pode ser nulo!");

        PanelField pf = readPanelFieldAnnotation(field);

        FieldUtilities pfu = new FieldUtilities();
        pfu.field = field;
        pfu.alias = alias;
        pfu.selection = isSelection(field, alias);
        if (pfu.selection)
            pfu.selectionFilterType = readSelectionFilterType(field);

        PropertiesReader pr = Application.get().getPropertiesReader();
        Properties p = new Properties();
        if (pr != null)
                p.putAll(pr.readFieldProperties(field));

        try{
            FieldDefinition fd = readFieldDefinitionAnnotation(pf, alias);
            pfu.label = fd.label();
            pfu.infoLabel = fd.infoLabel();
            pfu.index = fd.index();
            pfu.jumpToNextRow = fd.jumpNextRow();
            pfu.aligment = fd.alignment();
            pfu.bindOnFocusLost = true;
            pfu.bindReadOnly = false;
            pfu.type = DEFINITION;
            if (pr != null)
                p.putAll(pr.readFieldDefinitionProperties(fd));
            pfu.properties = p;
            return pfu;
        }catch(AnnotationsUtilitiesException aue){
            // não trata nada aqui pois a exceção é disparada no final
        }

        try{
            FieldEmbedded fe = readFieldEmbeddedAnnotation(pf, alias);
            pfu.call = fe.panel();
            pfu.index = fe.index();
            pfu.type = EMBEDDED;
            if (pr != null)
                p.putAll(pr.readFieldEmbeddedProperties(fe));
            pfu.properties = p;
            return pfu;
        }catch(AnnotationsUtilitiesException aue){
            // não trata nada aqui pois a exceção é disparada no final
        }

        try{
            FieldSelection fs = readFieldSelectionAnnotation(pf, alias);
            pfu.call = fs.panel();
            pfu.index = fs.index();
            pfu.search = fs.search();
            pfu.notFoundMessage = fs.notFoundMessage();
            pfu.type = SELECTION;
            if (pr != null)
                p.putAll(pr.readFieldSelectionProperties(fs));
            pfu.properties = p;
            return pfu;
        }catch(AnnotationsUtilitiesException aue){
            // não trata nada aqui pois a exceção é disparada no final
        }

        throw new AnnotationsUtilitiesException(
                "Erro ao criar \"PanelFieldUtilities\"!" +
                "\nAnnotation \"FieldDefinition\" \"FieldEmbedded\" " +
                "\"FieldSelection\" não encontrada para o nome\"" +
                alias +"\"!");
    }

    /**
     *
     * @param field
     * @return
     * @throws AnnotationsUtilitiesException
     */
    public static PanelField readPanelFieldAnnotation(Field field)
            throws AnnotationsUtilitiesException{

        if (field == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"field\" não pode ser nulo!");

        if(!field.isAnnotationPresent(PanelField.class))
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nAnnotation \"PanelField\" não encontrada!");

        return field.getAnnotation(PanelField.class);
    }
    /**
     *
     * @param pf
     * @param alias
     * @return
     * @throws AnnotationsUtilitiesException
     */
    public static FieldDefinition readFieldDefinitionAnnotation(
            PanelField pf, String alias) throws AnnotationsUtilitiesException{

        if (pf == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"panelField\" não pode ser nulo!");

        if (alias == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"alias\" não pode ser nulo!");

        for (int i = 0; i < pf.definitions().length; i++) {
            FieldDefinition fd = pf.definitions()[i];
            for (int j = 0; j < fd.alias().length; j++) {
                if (fd.alias()[j].equals(alias))
                    return fd;
            }
        }

        throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nAnnotation \"FieldDefinition\" não encontrada para o "
                    + "nome\""+ alias +"\"!");
    }

    /**
     *
     * @param pf
     * @param alias
     * @return
     * @throws AnnotationsUtilitiesException
     */
    public static FieldEmbedded readFieldEmbeddedAnnotation(
            PanelField pf, String alias) throws AnnotationsUtilitiesException{

        if (pf == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"panelField\" não pode ser nulo!");

        if (alias == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"alias\" não pode ser nulo!");

        for (int i = 0; i < pf.embedded().length; i++) {
            FieldEmbedded fe = pf.embedded()[i];
            for (int j = 0; j < fe.alias().length; j++) {
                if(fe.alias()[j].equals(alias))
                    return fe;
            }
        }

        throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nAnnotation \"FieldEmbedded\" não encontrada para o "
                    + "nome\""+ alias +"\"!");
    }

    /**
     *
     * @param pf
     * @param alias
     * @return
     * @throws AnnotationsUtilitiesException
     */
    public static FieldSelection readFieldSelectionAnnotation(
            PanelField pf, String alias) throws AnnotationsUtilitiesException{

        if (pf == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"panelField\" não pode ser nulo!");

        if (alias == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"alias\" não pode ser nulo!");

        for (int i = 0; i < pf.selection().length; i++) {
            FieldSelection fs = pf.selection()[i];
            for (int j = 0; j < fs.alias().length; j++) {
                if (fs.alias()[j].equals(alias))
                    return fs;
            }
        }

        throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nAnnotation \"FieldSelection\" não encontrada para o "
                    + "nome\""+ alias +"\"!");
    }

    /**
     *
     * @param list
     * @throws AnnotationsUtilitiesException
     */
    public static void sort(List<FieldUtilities> list)
            throws AnnotationsUtilitiesException{

        if (list == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"list\" não pode ser nulo!");

        //Ordena a lista
        Collections.sort(list, new Comparator<FieldUtilities>(){
            @Override
            public int compare(FieldUtilities o1, FieldUtilities o2) {

                int order1 = o1.index;
                int order2 = o2.index;

                // compara
                return order1 < order2 ? -1 : (order1 > order2 ? 1 : 0);

            }
        });
    }

    /**
     * Verifica se existe a anotação Selection com o alias.
     * 
     * @param field
     * @param alias
     * @return
     * @throws AnnotationsUtilitiesException
     */
    public static boolean isSelection(Field field, String alias)
            throws AnnotationsUtilitiesException{

        if (field == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"field\" não pode ser nulo!");

        if (alias == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"alias\" não pode ser nulo!");

        if (field.isAnnotationPresent(Selection.class)){
            Selection selection = field.getAnnotation(Selection.class);
            for (String s : selection.aliasForFields()) {
                if(s.equals(alias))
                    return true;
            }
        }

        return false;
    }

    public static FilterType readSelectionFilterType(Field field)
            throws AnnotationsUtilitiesException{

        if (field == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"field\" não pode ser nulo!");

        if (field.isAnnotationPresent(Selection.class)){
            Selection selection = field.getAnnotation(Selection.class);
            FilterType filterType = selection.filterType();
            if (filterType == FilterType.EQUALS)
                return filterType;
            else
                return FilterType.ILIKE;
        }

        throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nAnnotation \"Selection\" não encontrada!");
    }

    /**
     * Conta o número de colunas.
     * No caso do tipo ser "DEFINITION" o resultado será sempre um (1),
     * caso contrario dependera dos itens da lista "embedded".
     * @return O número de itens.
     */
    public int count(){
        if(type == DEFINITION)
            return 1;

        int count = 0;
        for (FieldUtilities pcu : embedded) {
            count += pcu.count();
        }
        return count;
    }
}
